package org.bookie.core.tests.framework;

import static org.easymock.EasyMock.createStrictControl;

import org.easymock.IMocksControl;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;

public class CommonAsserts extends Assert {
	private static class ControlSource extends ThreadLocal<IMocksControl>{
        /** Creates a strick control for <em>this</em> thread. */
        @Override
        protected IMocksControl initialValue()
        {
            return createStrictControl();
        }
    }

    private final ControlSource _source = new ControlSource();

    /**
     * Discards any mock objects created during the test. When using TestBase as a utility class,
     * not a base class, you must be careful to either invoke this method, or discard the TestBase
     * instance at the end of each test.
     */
    @AfterMethod(alwaysRun = true)
    public final void cleanupControlSource(){
        // TestNG reuses the same class instance across all tests within that
        // class, so if we don't
        // clear out the mocks, they will tend to accumulate. That can get
        // expensive, and can
        // cause unexpected cascade errors when an earlier test fails.

        // After each method runs, we clear this thread's mocks control.
        _source.remove();
    }

    /**
     * Creates a new mock object of the indicated type. The created object is retained for the
     * duration of the test (specifically to support {@link #replay()} and {@link #verify()}).
     * 
     * @param <T>
     *            the type of the mock object
     * @param mockClass
     *            the class to mock
     * @return the mock object, ready for training
     */
    public final <T> T newMock(Class<T> mockClass){
        return getMocksControl().createMock(mockClass);
    }

    /**
     * Replay's the mocks control, preparing all mocks for testing.
     */
    public final void replay(){
        getMocksControl().replay();
    }

    /**
     * Verifies the mocks control, ensuring that all mocks completed all trained method invocations,
     * then resets the control to allow more training of the mocks.
     */
    public final void verify(){
        IMocksControl control = getMocksControl();

        control.verify();
        control.reset();
    }

    /** Returns the control object used for all mocks created by this test case. */
    public final IMocksControl getMocksControl()
    {
        return _source.get();
    }

    /**
     * Sets the return value for the most recent method call upon the mock.
     * 
     * @param returnValue
     *            value to be returned from the method call
     */
    @SuppressWarnings("unchecked")
    public final <T> void setReturnValue(T returnValue){
        getMocksControl().andReturn(returnValue);
    }

    /**
     * Trains a mock object to throw an exception.
     * 
     * @param throwable
     *            the exception to be thrown by the most recent method call on the mock
     */
    public final void setThrowable(Throwable throwable){
        getMocksControl().andThrow(throwable);
    }

    /**
     * Invoked to indicate code should not reach a point. This is typically used after code that
     * should throw an exception.
     */
    public final void unreachable(){
        fail("This code should not be reachable.");
    }
}
